**Final project proposal**
(#) Student Song Shi
(##) Motivational image
A few sentences describing how the image conforms with this year's theme:
This is an art work by Nele Azevedo called melting men. This scene perfectly depicted the power of time: melting ice, mottled stones, deading weeds...
The melting tiny men are also behaving like reflecting on something, perhaps something about time.
Hence, I choose this picture as this year's theme.
The phenomena of light in this scene are also significant. The rough dielectric BRDF of ice, the heterogeneous absorbing media inside ice. The hair like
grass features..
(##) Proposed features and points
* A list of features relevant to your image that you'd like to implement along with a tentative point breakdown which sums up to 16 points.
1. Environment Map Emitter (with importance sampling) -4pt
2. Simple Extra Geometry (cube) -1pt
3. Parellization with Nanothread/C++17 Execution Policy/OpenMP -2pt
4. Simple Extra Emitters (point light) -1pt
5. Heterogeneous Volumetric Participating Media (with integrator of Path Tracing; includes 15 points from homogeneous) -10pt
*5 Included:
{
*. Homogeneous Scattering Participating Media
*. Homogeneous Absorbing Media (Beer's Law)
*. Homogeneous Scattering Participating Media (with integrator of Path Tracing)
*. Heterogeneous Absorbing + Emissive Media (no scattering)
}
(##) Description of features
1. Environment Map Emitter.
For environment map, I assume it as another type of emitter. So first I add it to emitter list when parsing.
I also assume that the enviroment map is a sphere surrounding the scene which has infinite radius.
This sphere here is the scene.(Since scene is a surface in our implementation).
So scene now has its own sample() and pdf() function.
// ...in parser.cpp Color3f Scene::sample(EmitterRecord& rec, const Vec2f& rv) const{ HitRecord hit; const Ray3f ray = *rec.parent; ScatterRecord srec; m_background->sample(ray, hit, srec, rv, 0); Ray3f outRay(rec.o, srec.wo); Color3f col = background(outRay); rec.hit.t = ray.infinity; rec.hit.mat = m_background.get(); rec.wi = srec.wo; rec.emitter = this; rec.pdf = pdf(rec.o, srec.wo); return col/rec.pdf; } float Scene::pdf(const Vec3f& o, const Vec3f& v) const{ HitRecord hit; return m_background->pdf(Ray3f(), v, hit); }
// ...in parser.cpp if (j.contains("background")) { m_background = DartsFactoryI also add two class: env_texture.cpp and env_material.cpp. The texture cpp is trivial, which is almost same as image texture. There is some special handling for env_material class. First, in constructor, I need to precompute the sine weighted pixel value. Because when converting the solid angle measure to sphere coordinate measure, there is a sine term from Jacobian. If we are going to importance sample the environmental map from sphere coordinate, we need to take this into account. Then I use this to build a 2D distribution2D for sampling discrete uv by weighted pixel value.::create(j["background"]); m_emitters.add_child(make_shared (*this)); }
// ... in env_material.cpp float* func = new float[size.x * size.y]; for (int y = 0; y < envTextureImage.height(); y++) { float theta = ((float)y + .5f) * M_PI / (float)envTextureImage.height(); float sinTheta = sin(theta); for(int x = 0; x < envTextureImage.width(); x++){ float max_in_rgbChannels = linalg::maxelem(envTextureImage(x, y)); func[y * envTextureImage.width() + x] = max_in_rgbChannels; func[y * envTextureImage.width() + x] *= sinTheta; } }For the pdf, there is two conversion. First is from square 2D distribution to rectangular. Then from rec to sphere coordinate. This is where the denominator comes from.
float EnvMaterial :: pdf(const Ray3f& ray, const Vec3f& scattered, const HitRecord& hit) const{ if(m_distribution2D == nullptr){ return INV_FOURPI; } Vec3f dir = normalize(scattered); Vec3f dir_rh_tocenter(dir.x, dir.y, -dir.z); Vec2f uv = Spherical::direction_to_equirectangular(dir_rh_tocenter); float pdf = m_distribution2D->pdf(uv) / (2 * M_PI * M_PI * sin(uv.y * M_PI)); return pdf; }There are some validation tests. Sine weighted texture:
// ... in scene.cpp drjit::blocked_range4. Point light. For point light, I added point.cpp as a special shape. The most useful part is the sample() function. And because the pdf of hitting the point light is a delta distribution, I need to fill in the rec.pdf as nanf. This is to avoid the pdf being used in the MIS calculation. When sampling MIS, the pdf and the Le term(which is also a delta distrubution) will cancel out. Leaving the mis_weight as 1.0f.range(0, image.width() * image.height()); // Use parallel_for to process each pixel in parallel drjit::parallel_for(range, [&](const drjit::blocked_range & r) { for (int index = r.begin(); index < r.end(); ++index) { int x = index % image.width(); int y = index / image.width(); Color3f color(0, 0, 0); for (int i = 0; i < m_num_samples; i++) { auto ray = m_camera->generate_ray(Vec2f((float)x, (float)y) + m_sampler->next2f()); if (m_integrator != nullptr) { color += m_integrator->Li(*this, *m_sampler, ray, 0) / (float)m_num_samples; } else { color += (recursive_color(ray, 0)) / (float)m_num_samples; } } image(x, y) = color; ++progress; } });
// ... in path_tracer_mis.cpp float mis_weight_light = 0; if (isnanf(erec.pdf)) { // mis_weight_light = 1.0f / scene.emitters().child_prob(); This is wrong. // ((alpha * delta / alpha * delta + const) = 1.0f) mis_weight_light = 1.0f; } // Normal case. else { mis_weight_light = std::max(0.0f, erec.pdf / (erec.pdf + hit.mat->pdf(ray, erec.wi, hit))); }